﻿using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace SamizdatSearch.ConditionTree
{
  public partial class ConditionTreeControl
  {
    public ConditionTreeControl()
    {
      InitializeComponent();
    }

    #region ' Drag'n'Drop '

    #region ' Fields '

    private Point _startPoint;
    private bool _isDragging;
    private IDraggableTreeNode _dragSource;
    private IDraggableTreeNode _logicalDropTargetNode;
    private IDraggableTreeNode _directDropTargetNode;
    private TreeViewItem _directDropTargetViewItem;
    private TreeViewItem _logicalDropTargetViewItem;
    private DropLocationPosition _dropPosition;

    #endregion

    #region ' Event Handlers '

    private void OnMouseDown(object sender, MouseButtonEventArgs e)
    {
      if (e.LeftButton == MouseButtonState.Pressed)
        if (e.RightButton == MouseButtonState.Released)
        {
          _startPoint = e.GetPosition(this);
          _isDragging = true;
        }
    }
    private void OnMouseMove(object sender, MouseEventArgs e)
    {
      if (!_isDragging) return;
      if (e.LeftButton != MouseButtonState.Pressed) return;
      if (e.RightButton != MouseButtonState.Released) return;
      if (e.GetPosition(this).DistanceTo(_startPoint) < 5) return;
      _isDragging = true;
      var treeViewItem = ((DependencyObject)e.OriginalSource).GetVisualParent<TreeViewItem>();
      if (treeViewItem == null) return;
      _dragSource = treeViewItem.DataContext as IDraggableTreeNode;
      if (_dragSource == null) return;
      DragHelper.SetIsDraggedObject(treeViewItem, true);
      DragDrop.DoDragDrop(this, _dragSource, DragDropEffects.Move | DragDropEffects.Copy);
      DragHelper.SetIsDraggedObject(treeViewItem, false);
      ResetAttachedProperties();
    }
    private void OnDragOver(object sender, DragEventArgs e)
    {
      if (!GetData(e)) return;
      if (!_dragSource.CanDrop(_logicalDropTargetNode)) return;
      e.Effects = DragDropEffects.Copy | DragDropEffects.Move;
      DragHelper.SetDropPosition(_directDropTargetViewItem, _dropPosition);
      if (_logicalDropTargetViewItem == null) return;
      DragHelper.SetIsDropTarget(_logicalDropTargetViewItem, true);
    }
    private void OnDrop(object sender, DragEventArgs e)
    {
      if (!GetData(e)) return;
      if (!_dragSource.CanDrop(_logicalDropTargetNode)) return;
      int index = _directDropTargetNode.Index;
      if (_dropPosition == DropLocationPosition.SiblingBelow) index += 1;
      if (_dropPosition == DropLocationPosition.ChildInside) index = -1;
      if (_dropPosition == DropLocationPosition.ChildBelow) index = 0;
      _dragSource.Drop(_logicalDropTargetNode, index);
    }

    #endregion

    #region ' Utils '

    public static T GetVisualParent<T>(DependencyObject element)
      where T : DependencyObject
    {

      while (element != null)
      {
        element = VisualTreeHelper.GetParent(element);
        if (element is T) break;
      }

      return element as T;
    }
    public static IEnumerable<T> FindVisualChildren<T>(DependencyObject element)
        where T : DependencyObject
    {
      if (element == null) yield break;

      var count = VisualTreeHelper.GetChildrenCount(element);
      for (int i = 0; i < count; i++)
      {
        var child = VisualTreeHelper.GetChild(element, i);
        if (child is T)
          yield return (T)child;
        foreach (var res in FindVisualChildren<T>(child))
          yield return res;
      }
    }

    #endregion

    #region ' Implementation '

    private void ResetAttachedProperties()
    {
      if (_directDropTargetViewItem != null)
      {
        DragHelper.SetDropPosition(_directDropTargetViewItem, DropLocationPosition.None);
        _directDropTargetViewItem = null;
      }
      if (_logicalDropTargetViewItem != null)
      {
        DragHelper.SetIsDropTarget(_logicalDropTargetViewItem, false);
      }
    }
    private bool GetData(DragEventArgs e)
    {
      ResetAttachedProperties();

      e.Effects = DragDropEffects.None;

      _directDropTargetViewItem = ((DependencyObject)e.OriginalSource)
        .GetVisualParent<TreeViewItem>();
      if (_directDropTargetViewItem == null) return false;

      _directDropTargetNode = _directDropTargetViewItem.DataContext as IDraggableTreeNode;
      if (_directDropTargetNode == null) return false;

      var hasChildren = FindVisualChildren<TreeViewItem>(_directDropTargetViewItem).Any();

      _dropPosition = GetDropLocationPosition(e,
        _directDropTargetViewItem, hasChildren, _directDropTargetNode.CanHaveChildren);

      //      _text.Text = "Has children: " + anyChildren + "; \r\n"
      //        + "Can have children: " + targetNode.CanHaveChildren + "; \r\n" 
      //        + "Target node: " + targetNode.GetType().Name + "; \r\n"
      //        + "Position: " + _dropPosition + "; \r\n"
      //        ;

      _logicalDropTargetViewItem = GetLogicalDropTarget(_dropPosition,
        _directDropTargetNode.CanHaveChildren, hasChildren, _directDropTargetViewItem);

      _logicalDropTargetNode = _logicalDropTargetViewItem != null ?_logicalDropTargetViewItem.DataContext as IDraggableTreeNode : null;

      return true;
    }

    private static DropLocationPosition GetDropLocationPosition(DragEventArgs e, 
      TreeViewItem directDropTarget, bool anyChildren, bool canHaveChildren)
    {
      var headerArea = directDropTarget
        .FindVisualChildren<Border>()
        .First(p => p.Name == "Header");

      var y = e.GetPosition(headerArea).Y;
      var height = headerArea.ActualHeight;
      
      if (canHaveChildren)
      {
        if (y < height / 4) return DropLocationPosition.SiblingAbove;
        if (y < height * 3 / 4) return DropLocationPosition.ChildInside;
      }
      else
      {
        if (y < height/2) return DropLocationPosition.SiblingAbove;
      }
      return anyChildren 
        ? DropLocationPosition.ChildBelow 
        : DropLocationPosition.SiblingBelow;
    }
    private static TreeViewItem GetLogicalDropTarget(DropLocationPosition dropLocationPosition,
      bool canHaveChildren, bool anyChildren, TreeViewItem directDropTarget)
    {
      if (dropLocationPosition == DropLocationPosition.SiblingAbove)
        directDropTarget = GetVisualParent<TreeViewItem>(directDropTarget);
      if (dropLocationPosition == DropLocationPosition.SiblingBelow)
        if (!anyChildren || !canHaveChildren)
          directDropTarget = GetVisualParent<TreeViewItem>(directDropTarget);
      return directDropTarget;
    }

    #endregion

    #endregion
  }
}
